#ifndef __MTemplateManipulator_h
#define __MTemplateManipulator_h
//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc., and/or its licensors.  All
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related
// material (collectively the "Data") in these files contain unpublished
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its
// licensors,  which is protected by U.S. and Canadian federal copyright law
// and by international treaties.
//
// The Data may not be disclosed or distributed to third parties or be
// copied or duplicated, in whole or in part, without the prior written
// consent of Autodesk.
//
// The copyright notices in the Software and this entire statement,
// including the above license grant, this restriction and the following
// disclaimer, must be included in all copies of the Software, in whole
// or in part, and all derivative works of the Software, unless such copies
// or derivative works are solely in the form of machine-executable object
// code generated by a source language processor.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE,
// OR ARISING FROM A COURSE OF DEALING, USAGE, OR TRADE PRACTICE. IN NO
// EVENT WILL AUTODESK AND/OR ITS LICENSORS BE LIABLE FOR ANY LOST
// REVENUES, DATA, OR PROFITS, OR SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK AND/OR ITS LICENSORS HAS
// BEEN ADVISED OF THE POSSIBILITY OR PROBABILITY OF SUCH DAMAGES.
// ==========================================================================
//+
//
// Writing manipulator or their contains in some cases require additional
// classes for interacting with Maya. Below are some template classes
// for making the writing of manipulators or their containers easier.
//
// ****************************************************************************

#if defined __cplusplus

// ****************************************************************************
// INCLUDED HEADER FILES
#include <maya/MTypes.h>
#include <maya/MObject.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxSelectionContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MModelMessage.h>

// ****************************************************************************
// CLASS DECLARATION (MTemplateManipulator)

//! \ingroup OpenMayaUI
//! \brief Template class for context commands.
/*!
  Template helper to build command that is used to create context.

  \param[in] ContextCommandName : string name of context command
  \param[in] ContextCommandClass : context command derived from MPxContextCommand
  \param[in] ContextClass : context derived from MPxSelectionContext
*/
template <const char *ContextCommandName, class ContextCommandClass, class ContextClass>
class MTemplateContextCommand : public MPxContextCommand
{
public:
	//! Constructor
	MTemplateContextCommand() {}

	//! Command function for make the context
	virtual MPxContext* makeObj()
	{
		return new ContextClass();
	}

	//! Creator of the context command
	static void* creator()
	{
		return new ContextCommandClass;
	}

	/*!
	  Plug-in registration method. 

	  \param[in] obj plugin object taken from the initializePlugin()
	  entry point.
	*/
	static MStatus registerContextCommand( MObject obj )
	{
		MFnPlugin plugin( obj );
		return plugin.registerContextCommand(ContextCommandName,
										   creator);
	}

	/*! 
	  Plug-in deregistration method.

	  \param[in] obj plugin object taken from the initializePlugin()
	  entry point.
	*/
	static MStatus deregisterContextCommand( MObject obj )
	{
		MFnPlugin plugin( obj );
		return plugin.deregisterContextCommand(ContextCommandName);
	}
};

//! \ingroup OpenMayaUI
//! \brief Template class for selection context.
/*!
  Template helper to build selection contexts.

  \par Parameters:
  \li ContextNameString : string name of context command
  \li ContextClass : context command derived from MPxContextCommand
  \li NodeType : MFn type that selection context should operate on
  \li ManipulatorClass : derived from MPxManipulatorNode
  \li ManipulatorNodeName : name of the manipulator node
*/
template <const char *ContextNameString, class ContextClass, MFn::Type NodeType, class ManipulatorClass, const char *ManipulatorNodeName>
class MTemplateSelectionContext : public MPxSelectionContext
{
public:
	//! Constructor
	MTemplateSelectionContext()
	{
		MString str("Plug-in manipulator: ");
		str+= ContextNameString;
		setTitleString(str);
		manipulatorClassPtr = 0;
	}

	//! Destructor
	virtual ~MTemplateSelectionContext() {}

	/*! 
	  Sets up a active list modified message which will trigger
	  manipulator create/delete operations.

	  \param[in] event The event hat triggered it.
	*/
	virtual void toolOnSetup(MEvent &event)
	{
		MString str("Move the object using the manipulator");
		setHelpString(str);

		updateManipulators(this);
		MStatus status;
		activeListModifiedMsgId = MModelMessage::addCallback(MModelMessage::kActiveListModified,
										 updateManipulators,
										 this, &status);
		if (!status)
		{
			MGlobal::displayError("Model addCallback failed");
		}
	}

	//! Removes the callback
	virtual void toolOffCleanup()
	{
		MStatus status;
		status = MModelMessage::removeCallback(activeListModifiedMsgId);
		if (!status)
		{
			MGlobal::displayError("Model remove callback failed");
		}
		MPxContext::toolOffCleanup();
	}

	/*!
	  Override to specify which attributes are required on the
	  selected geometry.

	  \param[in] namesOfAttributes Array of attribute names.
	*/
	virtual void namesOfAttributes(MStringArray& namesOfAttributes)
	{
		namesOfAttributes.clear();
	}

	/*! Override to set manipulator initial state.

	\note manipulatorClassPtr and firstObjectSelected will be set on
	entry. manipulatorClassPtr is the manipulator created and
	firstObjectSelected can be used to position the manipulator in the
	correct position.
	*/
	virtual void setInitialState()
	{
		// No-op in default state
	}

	//! Ensure that valid geometry is selected
	bool validGeometrySelected()
	{
		MStatus status = MStatus::kSuccess;

		MSelectionList list;
		status = MGlobal::getActiveSelectionList(list);
		MItSelectionList iter(list, MFn::kInvalid, &status);

		if (MStatus::kSuccess == status)
		{
			for (; !iter.isDone(); iter.next())
			{
				MObject dependNode;
				iter.getDependNode(dependNode);
				if (dependNode.isNull() || !dependNode.hasFn(NodeType))
				{
					MGlobal::displayWarning("Object in selection list is not right type of node");
					return false;
				}

				MFnDependencyNode dependNodeFn(dependNode);
				MStringArray attributeNames;
				namesOfAttributes( attributeNames );
				unsigned int i;
				for ( i = 0; i < attributeNames.length(); i++ )
				{
					MPlug plug = dependNodeFn.findPlug(attributeNames[i], &status);
					if ( plug.isNull() )
					{
						MGlobal::displayWarning("Object cannot be manipulated: " +
							dependNodeFn.name());
						return false;
					}
				}
			}
			return true;
		}
		return false;
	}

	/*!
	  Callback that creates the manipulator if valid geometry is
	  selected. Also removes the manipulator if no geometry is
	  selected. Handles connecting the manipulator to multiply
	  selected nodes.

	  \param[in] data Pointer to the current context class.
	*/
	static void updateManipulators(void * data)
	{
		MStatus status = MStatus::kSuccess;

		ContextClass * ctxPtr = (ContextClass *) data;
		ctxPtr->deleteManipulators();

		if ( ! ctxPtr->validGeometrySelected() )
			return;

		// Clear info
		ctxPtr->manipulatorClassPtr = 0;
		ctxPtr->firstObjectSelected = MObject::kNullObj;

		MSelectionList list;
		status = MGlobal::getActiveSelectionList(list);
		MItSelectionList iter(list, MFn::kInvalid, &status);

		if (MStatus::kSuccess == status)
		{
			MString manipName (ManipulatorNodeName);
			MObject manipObject;
			ManipulatorClass* manipulator =
				(ManipulatorClass *) ManipulatorClass::newManipulator(manipName, manipObject);

			if (NULL != manipulator)
			{
				// Save state
				ctxPtr->manipulatorClassPtr = manipulator;
				// Add the manipulator
				ctxPtr->addManipulator(manipObject);
				//
				for (; !iter.isDone(); iter.next())
				{
					MObject dependNode;
					iter.getDependNode(dependNode);
					MFnDependencyNode dependNodeFn(dependNode);
					// Connect the manipulator to the object in the selection list.
					if (!manipulator->connectToDependNode(dependNode))
					{
						MGlobal::displayWarning("Error connecting manipulator to"
							" object: " + dependNodeFn.name());
						continue;
					}
					//
					if ( MObject::kNullObj == ctxPtr->firstObjectSelected )
						ctxPtr->firstObjectSelected = dependNode;
				}

				// Allow the manipulator to set initial state
				ctxPtr->setInitialState();
			}
		}
	}

protected:
	MObject firstObjectSelected;
	ManipulatorClass *manipulatorClassPtr;
private:
	MCallbackId activeListModifiedMsgId;
};

#endif /* _cplusplus */
#endif /* __MTemplateManipulator_h */

